package com.xuecheng.media.utils;

import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author Tang
 * @version 1.0
 * @date 2023/12/31 18:00
 */
@Slf4j
@Component
public class MinIOFileOperationUtils {
    public final static String MEDIA_PROCESS_FAIL = "3";
    public final static String MEDIA_PROCESS_SUCCESS = "2";
    public final static String DOT_MP4 = ".mp4";
    public static MinioClient minioClient;
    /** 存储视频 **/
    private static String BUCKET_NAME_VIDEO;
    public MinIOFileOperationUtils(MinioClient minioClient,
                                   @Value("${minio.bucket.videofiles}") String bucketName) {
        MinIOFileOperationUtils.minioClient = minioClient;
        MinIOFileOperationUtils.BUCKET_NAME_VIDEO = bucketName;
    }

    public static String getFileContentMD5(File file) {
        try {
            @Cleanup FileInputStream fi = new FileInputStream(file);
            return DigestUtils.md5Hex(fi);
        } catch (IOException e) {
            log.error("获取文件内容的md5值失败 {}",e);
            return "";
        }
    }

    // 根据扩展名获取mimeType
    public static String getMimeType(String extension) {
        if (extension == null) {
            extension = "";
        }
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
        // 通用的mimeType 字节流
        return extensionMatch == null ?
                MediaType.APPLICATION_OCTET_STREAM_VALUE :
                extensionMatch.getMimeType();
    }

    public static boolean uploadFileToMinio(String bucketName,
                                            String localFilePath,
                                            String objectName,
                                            String mimeType) {
        try {
            UploadObjectArgs build = UploadObjectArgs.builder()
                    .bucket(bucketName)
                    .filename(localFilePath)
                    .object(objectName)
                    // 设置媒体类型
                    .contentType(mimeType)
                    .build();
            minioClient.uploadObject(build);
            return true;
        } catch (Exception e) {
            log.error("上传文件出错, bucketName:{}, objectName:{}, {}",bucketName, objectName, e);
            return false;
        }
    }

    public static File downFileFromMinIO(String bucketName, String objectName) {
        GetObjectArgs build = GetObjectArgs
                .builder()
                .bucket(bucketName)
                .object(objectName)
                .build();
        try (FilterInputStream fi = minioClient.getObject(build)) {
            File downFile = File.createTempFile("minio", ".tmp");
            @Cleanup FileOutputStream fo = new FileOutputStream(downFile);
            IOUtils.copy(fi, fo);
            return downFile;
        } catch (Exception e) {
            log.error("从minio下载文件失败 bucketName:{} objectName:{} 错误信息:{}",
                    bucketName,
                    objectName,
                    e.getMessage());
            return null;
        }
    }

    public static void clearMinIOChunkFile(String chunkFilePath, int chunkTotal) {
        List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i)
                .limit(chunkTotal)
                .map(i -> new DeleteObject(chunkFilePath.concat(Integer.toString(i))))
                .collect(Collectors.toList());
        Iterable<Result<DeleteError>> objects = minioClient.removeObjects(RemoveObjectsArgs.builder()
                .bucket(BUCKET_NAME_VIDEO)
                .objects(deleteObjects)
                .build());
        objects.forEach(obj -> {
            try {
                obj.get();
            } catch (Exception e) {
                log.error("清理分块异常, 分块文件路径:{} 错误信息:{}",
                        chunkFilePath,
                        e.getMessage());
            }
        });
    }

    // 根据当前事件存储minio路径，默认 年/月/日
    public static String getDefaultFolderPath() {
        return new SimpleDateFormat("yyyy-MM-dd").format(new Date()).replace("-", "/") + "/";
    }

    public static String getMinIOChunkFilePath(String md5) {
        if (StringUtils.isEmpty(md5)) {
            return "";
        }
        return md5.charAt(0) + "/" + md5.charAt(1) + "/" + md5 +"/chunk/";
    }

    public static String getMinIOMergeFilePath(String md5, String fileExt) {
        if (StringUtils.isEmpty(md5)) {
            return "";
        }
        return md5.charAt(0) + "/" + md5.charAt(1) + "/" + md5 +"/" + md5 + fileExt;
    }

    public static void verifyMinIOFile(File file, String fileMd5) {
        try (FileInputStream fi = new FileInputStream(file)) {
            String mergeFileMd5 = DigestUtils.md5Hex(fi);
            if (!fileMd5.equals(mergeFileMd5)) {
                log.error("与合并文件的md5值不一致, sourceFileMd5:{} mergeFileMd5:{}",
                        fileMd5,
                        mergeFileMd5);
                return;
            }
        } catch (Exception e) {
            log.error("与合并文件的md5值不一致, bucketName:{} 错误信息:{}",
                    BUCKET_NAME_VIDEO,
                    e.getMessage());
            file.delete();
            return ;
        }
        file.delete();
    }

    public static void mergeMinIOFile(int chunkTotal, String chunkFilePath, String objectName) {
        // 将minio上的分块文件进行合并
        List<ComposeSource> sources = Stream.iterate(0, i -> ++i)
                .limit(chunkTotal)
                .map(i -> ComposeSource.builder()
                        .bucket(BUCKET_NAME_VIDEO)
                        .object(chunkFilePath + i)
                        .build())
                .collect(Collectors.toList());

        ComposeObjectArgs build = ComposeObjectArgs.builder()
                .bucket(BUCKET_NAME_VIDEO)
                // 最终合并后的文件
                .object(objectName)
                .sources(sources)
                .build();
        try {
            minioClient.composeObject(build);
        } catch (Exception e) {
            log.error("合并文件出差 bucketName:{} objectName:{} 错误信息:{}",
                    BUCKET_NAME_VIDEO,
                    objectName,
                    e.getMessage());
        }
    }
}
